 #include "sal.h"
#include <pthread.h>

#include <math.h>

#ifdef SAL_DEBUG_MUTEX
struct sal_mutex
{
    pthread_mutex_t lock;
    uint32 is_used;
    pid_t tid;
    char lk_hold_location[SAL_LOC_NAME_MAX];
    char ulk_hold_location[SAL_LOC_NAME_MAX];
};

sal_task_mutex_t g_task_mutex;
static sal_mutex_t* mutex_mutex;

char location1[SAL_LOC_NAME_MAX] = "mem_mgr_add_to_mtype_table";
char location2[SAL_LOC_NAME_MAX] = "mem_mgr_remove_from_mtype_table";
#else
struct sal_mutex
{
    pthread_mutex_t lock;
};
#endif


struct sal_sem
{
    sem_t sem;
};

sal_err_t
ctc_sal_mutex_create(sal_mutex_t** pmutex)
{
    sal_mutex_t* mutex;

    SAL_MALLOC(mutex, sal_mutex_t*, sizeof(sal_mutex_t));
    if (!mutex)
    {
        return ENOMEM;
    }

    pthread_mutex_init(&mutex->lock, NULL);

#ifdef SAL_DEBUG_MUTEX
    mutex->is_used = 0;
    mutex->tid = 0;
    sal_memset(mutex->lk_hold_location, 0, SAL_LOC_NAME_MAX);
    sal_memset(mutex->ulk_hold_location, 0, SAL_LOC_NAME_MAX);
#endif

    *pmutex = mutex;
    return 0;
}

void
ctc_sal_mutex_destroy(sal_mutex_t* mutex)
{
    if (!mutex)
    {
        return ;
    }
    while (pthread_mutex_destroy(&mutex->lock)==EBUSY)
    {
        sal_task_sleep(1);
    }

    SAL_FREE(mutex);
}

#ifdef SAL_DEBUG_MUTEX
void
_ctc_sal_mutex_free_db()
{
    uint8 i = 0;
    uint8 j = g_task_mutex.tid_num -1;
    sal_time_t current_time = 0;
    sal_time_t old_time = 0;
    sal_time_t old_time2 = 0;

    for(i = 0; i<= j; i++)
    {
        sal_time(&current_time);
        old_time = g_task_mutex.info[i]->time;
        if((current_time - old_time) < 300) /*timer 5min*/
        {
            continue;
        }
        else
        {
            if(i < j)
            {
                /*find active task from last, and move to current loaction*/
                for ( ; j >= i; j--)
                {
                    old_time2 = g_task_mutex.info[j]->time;
                    if((current_time - old_time2) < 300) /*timer 5min*/
                    {
                        SAL_FREE(g_task_mutex.info[i]);
                        g_task_mutex.info[i] = g_task_mutex.info[j];
                        g_task_mutex.info[j] = NULL;
                        g_task_mutex.tid_num--;
                        j = g_task_mutex.tid_num - 1;
                        break;
                    }
                    else
                    {
                        SAL_FREE(g_task_mutex.info[j]);
                        g_task_mutex.tid_num--;
                        continue;
                    }
                }
            }
            else
            {
                SAL_FREE(g_task_mutex.info[i]);
                g_task_mutex.tid_num--;
                continue;
            }
        }
    }
}

void
ctc_sal_mutex_lock(sal_mutex_t* mutex, const char *take_loc, int line)
{
    char lineNum[8] = {'\0'};
    pid_t tid = ctc_sal_gettid();
    uint32 i = 0;
    sal_task_mutex_info_t* tmp_info = NULL;
    sal_task_mutex_info_t* conflict_info = NULL;
    char req_location[SAL_LOC_NAME_MAX] = {0};

    if (!mutex)
    {
        return ;
    }

    sal_sprintf(lineNum, ":%d", line);
    sal_strncpy(req_location, take_loc, SAL_LOC_NAME_MAX - 7);
    sal_strcat(req_location, lineNum);

    if(mutex_mutex)
    {
        /*for multi task*/
        pthread_mutex_lock(&mutex_mutex->lock);
    }

    if(!(!sal_strcmp(take_loc,location1) || !sal_strcmp(take_loc,location2)))
    {
        if(g_task_mutex.tid_num > SAL_MUTEX_UPDATE_THRD)
        {
            /*remove not active task db*/
            _ctc_sal_mutex_free_db();
        }

        for(i = 0; i< g_task_mutex.tid_num; i++)
        {
            if(g_task_mutex.info[i]->tid == tid)
            {
                tmp_info = g_task_mutex.info[i];
                tmp_info->req_lk = mutex;
                sal_strncpy(tmp_info->lk_req_location, req_location, SAL_LOC_NAME_MAX);
                break;
            }
        }

        /*task Id first lock, need create db*/
        if(!tmp_info && g_task_mutex.init)
        {
            SAL_MALLOC(tmp_info, sal_task_mutex_info_t*, sizeof(sal_task_mutex_info_t));
            if (!tmp_info)
            {
                CTC_SAL_DEBUG_OUT("[MUTEX] No Memory!!!\n");
                if(mutex_mutex)
                {
                    /*for multi task*/
                    pthread_mutex_unlock(&mutex_mutex->lock);
                }
                return;
            }
            sal_memset(tmp_info,0,sizeof(sal_task_mutex_info_t));
            sal_strncpy(tmp_info->lk_req_location, req_location, SAL_LOC_NAME_MAX);
            tmp_info->tid = tid;
            tmp_info->req_lk = mutex;
            /*update db*/
            g_task_mutex.info[g_task_mutex.tid_num] = tmp_info;
            g_task_mutex.tid_num +=1;
        }

        /*check Self-Locking or Interlocked*/
        if(tmp_info && mutex->is_used)
        {
            if(tid == mutex->tid)
            {
                CTC_SAL_DEBUG_OUT("\nExistential Self-Locking!!!\n");
                CTC_SAL_DEBUG_OUT("%-10s, %-64s, %-64s\n","Task Id", "hold Lock Location", "Request Lock Location");
                CTC_SAL_DEBUG_OUT("%-10d, %-64s, %-64s\n",mutex->tid, mutex->lk_hold_location, tmp_info->lk_req_location);
                tmp_info->req_lk = NULL;
                if(mutex_mutex)
                {
                    /*for multi task*/
                    pthread_mutex_unlock(&mutex_mutex->lock);
                }
                return;
            }

            for(i = 0; i< g_task_mutex.tid_num; i++)
            {
                if(g_task_mutex.info[i]->tid != mutex->tid)
                {
                    continue;
                }

                conflict_info = g_task_mutex.info[i];

                if(conflict_info->req_lk && conflict_info->req_lk->is_used && conflict_info->req_lk->tid == tid)
                {
                    CTC_SAL_DEBUG_OUT("\nExistential Interlocked!!!\n");
                    CTC_SAL_DEBUG_OUT("%-10s, %-64s, %-64s\n","Task Id", "hold Lock Location", "Request Lock Location");
                    CTC_SAL_DEBUG_OUT("%-10d, %-64s, %-64s\n",mutex->tid, mutex->lk_hold_location, conflict_info->lk_req_location);
                    CTC_SAL_DEBUG_OUT("%-10d, %-64s, %-64s\n",tid, conflict_info->req_lk->lk_hold_location, tmp_info->lk_req_location);
                    tmp_info->req_lk = NULL;
                    if(mutex_mutex)
                    {
                        /*for multi task*/
                        pthread_mutex_unlock(&mutex_mutex->lock);
                    }
                    return;
                }
                break;
            }
        }
    }

    if(mutex_mutex)
    {
        /*for multi task*/
        pthread_mutex_unlock(&mutex_mutex->lock);
    }

    pthread_mutex_lock(&mutex->lock);

    if(mutex_mutex)
    {
        /*for multi task*/
        pthread_mutex_lock(&mutex_mutex->lock);
    }

    mutex->tid = tid;
    mutex->is_used = 1;

    sal_strncpy(mutex->lk_hold_location, req_location, SAL_LOC_NAME_MAX);

    if(tmp_info)
    {
        tmp_info->req_lk = NULL;
        tmp_info->hold_lk[tmp_info->hold_lk_cnt] = mutex;
        tmp_info->hold_lk_cnt++;
        sal_time(&(tmp_info->time));
    }

    if(mutex_mutex)
    {
        /*for multi task*/
        pthread_mutex_unlock(&mutex_mutex->lock);
    }

    return;
}


void
ctc_sal_mutex_unlock(sal_mutex_t* mutex, const char *give_loc, int line)
{
    pid_t tid = ctc_sal_gettid();
    sal_task_mutex_info_t* tmp_mutex_info = NULL;
    sal_mutex_t* tmp_mutex = NULL;
    char lineNum[8] = {'\0'};
    char req_location[SAL_LOC_NAME_MAX] = {0};
    uint32 tmp_cnt = 0;
    uint32 i = 0;
    uint32 j = 0;

    if (!mutex)
    {
        return ;
    }

    sal_sprintf(lineNum, ":%d", line);
    sal_strncpy(req_location, give_loc, SAL_LOC_NAME_MAX - 7);
    sal_strcat(req_location, lineNum);
    if(mutex_mutex)
    {
        /*for multi task*/
        pthread_mutex_lock(&mutex_mutex->lock);
    }

    if(!(!sal_strcmp(give_loc,location1) || !sal_strcmp(give_loc,location2)))
    {
        if(!mutex->is_used || mutex->tid != tid)
        {
            CTC_SAL_DEBUG_OUT("\nERROR Happen : %s\n", ((!mutex->is_used) ? "Have Been Unlocked!!!" : "NOT Expect Task Id!!!"));
            CTC_SAL_DEBUG_OUT("Current Task Id : %-10d\n", tid);
            CTC_SAL_DEBUG_OUT("Current Request UnLock Location : %-64s\n", req_location);
            if(!mutex->is_used)
            {
                CTC_SAL_DEBUG_OUT("Last UnLock Location            : %-64s\n", mutex->ulk_hold_location);
                CTC_SAL_DEBUG_OUT("Last Lock Location              : %-64s\n", mutex->lk_hold_location);
            }
            else
            {
                CTC_SAL_DEBUG_OUT("\nHold Task Id :%-10d\n", mutex->tid);
                CTC_SAL_DEBUG_OUT("Hold Lock Location              : %-64s\n", mutex->lk_hold_location);
            }
            if(mutex_mutex)
            {
                /*for multi task*/
                pthread_mutex_unlock(&mutex_mutex->lock);
            }
            return;
        }

        for(i = 0; i< g_task_mutex.tid_num; i++)
        {
            if(g_task_mutex.info[i]->tid != tid)
            {
                continue;
            }

            tmp_mutex_info = g_task_mutex.info[i];
            tmp_cnt = tmp_mutex_info->hold_lk_cnt - 1;

            sal_strncpy(tmp_mutex_info->ulk_req_location , req_location, SAL_LOC_NAME_MAX);

            for(j = 0; j < tmp_mutex_info->hold_lk_cnt; j++)
            {
                if(mutex != tmp_mutex_info->hold_lk[j])
                {
                    continue;
                }

                tmp_mutex = tmp_mutex_info->hold_lk[tmp_cnt];

                if(j != tmp_cnt)
                {
                    CTC_SAL_DEBUG_OUT("\nERROR Happen : Not latest Lock!!!\n");
                    CTC_SAL_DEBUG_OUT("Task %d should UnLock Location : %-64s,  Current UnLock Location : %-64s\n",tid, tmp_mutex->lk_hold_location, req_location);
                    if(mutex_mutex)
                    {
                        /*for multi task*/
                        pthread_mutex_unlock(&mutex_mutex->lock);
                    }
                    return;
                }
                break;
            }
            break;
        }

    }

    sal_strncpy(mutex->ulk_hold_location, req_location, SAL_LOC_NAME_MAX);

    if(tmp_mutex_info)
    {
        tmp_mutex_info->hold_lk[tmp_cnt] = NULL;
        tmp_mutex_info->hold_lk_cnt = tmp_cnt;
    }

    mutex->is_used = 0;
    mutex->tid = 0;

    if(mutex_mutex)
    {
        /*for multi task*/
        pthread_mutex_unlock(&mutex_mutex->lock);
    }

    pthread_mutex_unlock(&mutex->lock);

    return;
}

void
ctc_sal_mutex_dump(pid_t tid)
{
    sal_task_mutex_info_t* tmp_mutex_info = NULL;
    uint8 all = !tid;
    uint8 i = 0;
    uint8 j = 0;
    if(mutex_mutex)
    {
        /*for multi task*/
        pthread_mutex_lock(&mutex_mutex->lock);
    }

    for(i = 0; i< g_task_mutex.tid_num; i++)
    {
        tmp_mutex_info = g_task_mutex.info[i];
        if(all || tid == tmp_mutex_info->tid)
        {
            CTC_SAL_DEBUG_OUT("\n===========================================================================\n");
            CTC_SAL_DEBUG_OUT("Task Id : %-10d\n", tmp_mutex_info->tid);
            CTC_SAL_DEBUG_OUT("Request Lock Location : %-64s\n", (tmp_mutex_info->req_lk ? tmp_mutex_info->lk_req_location : "-"));
            for(j = 0; j< tmp_mutex_info->hold_lk_cnt ; j++)
            {
                CTC_SAL_DEBUG_OUT("Hold Lock NO.%-2d Location : %-64s\n", j, tmp_mutex_info->hold_lk[j]->lk_hold_location);
            }
            if(!all)
            {
                break;
            }
        }
    }
    if(mutex_mutex)
    {
        /*for multi task*/
        pthread_mutex_unlock(&mutex_mutex->lock);
    }
    CTC_SAL_DEBUG_OUT("===========================================================================\n");
}

sal_err_t
ctc_sal_mutex_init(void)
{
    int ret = 0;

    if(!g_task_mutex.init)
    {
        sal_memset(&g_task_mutex,0,sizeof(sal_task_mutex_t));
        ret = ctc_sal_mutex_create(&mutex_mutex);
        g_task_mutex.init = 1;
    }
    return ret;
}

void
ctc_sal_mutex_deinit(void)
{
    uint8 i = 0;

    ctc_sal_mutex_destroy(mutex_mutex);

    for(i = 0; i < g_task_mutex.tid_num; i++)
    {
        SAL_FREE(g_task_mutex.info[i]);
        break;
    }
    sal_memset(&g_task_mutex,0,sizeof(sal_task_mutex_t));
    g_task_mutex.init = 0;
}

#else

void
ctc_sal_mutex_lock(sal_mutex_t* mutex)
{
    if (!mutex)
    {
        return ;
    }

    pthread_mutex_lock(&mutex->lock);
}

void
ctc_sal_mutex_unlock(sal_mutex_t* mutex)
{
    if (!mutex)
    {
        return ;
    }

    pthread_mutex_unlock(&mutex->lock);
}
#endif

bool
ctc_sal_mutex_try_lock(sal_mutex_t* mutex)
{
    if (!mutex)
    {
        return FALSE;
    }

    return pthread_mutex_trylock(&mutex->lock) != EBUSY;
}

sal_err_t
ctc_sal_sem_create(sal_sem_t** pp_sem, uint32 init_count)
{
    sal_sem_t* p_sem = NULL;

    SAL_MALLOC(p_sem, sal_sem_t*, sizeof(sal_sem_t));
    if (!p_sem)
    {
        return ENOMEM;
    }

    sem_init(&p_sem->sem, 0, init_count);
    *pp_sem = p_sem;

    return 0;
}

sal_err_t
ctc_sal_sem_destroy(sal_sem_t* p_sem)
{
    if (!p_sem)
    {
        return EINVAL;
    }

    sem_destroy(&p_sem->sem);
    SAL_FREE(p_sem);

    return 0;
}

sal_err_t
ctc_sal_sem_take(sal_sem_t* p_sem, int32 msec)
{
    int32 ret = 0;

    if (!p_sem)
    {
        return EINVAL;
    }

    if (SAL_SEM_FOREVER == msec)
    {
        do
        {
            ret = sem_wait(&p_sem->sem);
        }
        while (ret != 0 && EINTR == errno);
    }
    else
    {
        int32 time_wait = 1;

        for (;;)
        {
            if (sem_trywait(&p_sem->sem) == 0)
            {
                ret = 0;
                break;
            }

            if (errno != EAGAIN && errno != EINTR)
            {
                ret = errno;
                break;
            }

            if (time_wait > msec)
            {
                time_wait = msec;
            }

            ctc_sal_task_sleep(time_wait);
            msec -= time_wait;

            if (msec == 0)
            {
                ret = ETIMEDOUT;
                break;
            }

            if ((time_wait *= 2) > 100)
            {
                time_wait = 100;
            }
        }
    }

    return ret;
}

sal_err_t
ctc_sal_sem_give(sal_sem_t* p_sem)
{
    if (!p_sem)
    {
        return EINVAL;
    }
    return sem_post(&p_sem->sem);
}

/**
 * ctc_sal_intlog2 - computes the log2 value
 * @value: the number to be calculated
 *
 * Computes: log2(value) * 2^24
 */
// uint64
// ctc_sal_intlog2(uint32 value)
// {
//     /*no use*/
//     uint64 log;
//     log = (uint64)(log2(value) * 16777216);
//     return log;
// }

/**
 * ctc_sal_intlog10 - computes the log10 value
 * @value: the number to be calculated
 *
 * Computes: log10(value) * 2^24
 */
uint64
ctc_sal_intlog10(uint32 value)
{
    uint64 log;
    log = (uint64)(log10(value) * 16777216);
    return log;
}

/**
 * ctc_sal_intpow - computes the exponentiation of the given base and exponent
 * @base: base which will be raised to the given power
 * @exp: power to be raised to
 *
 * Computes: pow(base, exp), i.e. @base raised to the @exp power
 */
uint64
ctc_sal_intpow(uint64 base, uint32 exp)
{
    float temp;
    temp = (uint64)pow(base, exp);
    return temp;
}

/**
 * ctc_sal_intround - cumpute the integer round of base
 * @base: the value to be rounded
 * @factor: the factor of round value
 *
 * Computes: floor(round(base*factor))
 */
uint32
ctc_sal_intround(uint32 base, uint32 factor)
{
    base += factor;
    base -= base % (factor*2);
    return base;
}

/**
 * ctc_sal_intsqrt - computes the integer square root
 * @x: integer of which to calculate the sqrt
 *
 * Computes: floor(sqrt(x))
 */
uint64
ctc_sal_intsqrt(uint64 num)
{
    uint64 x = num;
    x = (uint64)sqrt(num);
    return x;
}
